﻿using System;
using System.Collections.Generic;
using AnimatingHair.Entity;
using OpenTK;
using AnimatingHair.Auxiliary;

namespace AnimatingHair.Initializing
{
    // TODO: urobit robustnu reprezentaciu toho, co budem potrebovat nakoniec - tj nejaky height map L(u,v)

    class HeuristicBeamDistributor : IParticleDistributor
    {
        // TODO: interaktivna aplikacia?
        private const double elevationFactor1 = 0.2;
        private const double elevationFactor2 = 0.37;
        private const double gravityFactor = 0.3;

        private SphereCoordinateGenerator scg;
        private BeamSimulator bs;
        private Geometry.Sphere head;
        private Random r;

        public HeuristicBeamDistributor( int seed, Geometry.Sphere head )
        {
            r = new Random( seed );
            this.head = head;
            scg = new SphereCoordinateGenerator( Vector3d.Zero, head.Radius, seed );
            bs = new BeamSimulator( head );
        }

        public IEnumerable<ParticleCoordinate> DistributeParticles( int particleCount )
        {
            ParticleCoordinate[] result = new ParticleCoordinate[ particleCount ];

            // tu prebieha nejake moje ad-hoc rovnomerne nahodne rozmiestnovanie (vysledkom sa to asi podoba poissonovi)
            int k = 0;
            double minDistance = 100; // TODO: 100?

            while ( k < particleCount )
            {
                bool badPosition = false;
                SphericalCoordinate sc;
                sc = scg.GenerateRandomSphericalCoordinate();
                ParticleCoordinate newCoordinate = bs.TransformCoordinate( sc.Azimuth, sc.Elevation, (Const.HairLength + Const.s_r) * r.NextDouble() - Const.s_r );
                for ( int i = 0; i < k; i++ )
                {
                    if ( (newCoordinate.Position - result[ i ].Position).Length < minDistance ) // TODO: skus nejak zvysit koncetraciu suradnic s mensim S
                    {
                        badPosition = true;
                        minDistance *= 0.99;
                        break;
                    }
                }

                if ( !badPosition )
                {
                    result[ k ] = newCoordinate;
                    k++;
                    minDistance = 100;
                }
            }

            return result;
        }

        class SphereCoordinateGenerator
        {
            private Vector3d center;
            private double radius;
            private Random r;

            public SphereCoordinateGenerator( Vector3d center, double radius, int seed )
            {
                r = new Random( seed );
                this.center = center;
                this.radius = radius;
            }

            public SphericalCoordinate GenerateRandomSphericalCoordinate()
            {
                double elevationSentinel = 0.5 * Math.PI * r.NextDouble();
                double elevation = Math.Sin( elevationSentinel ) * 0.5 * Math.PI * r.NextDouble();
                //double elevation = 0.5 * Math.PI * r.NextDouble();
                double azimuth = -Math.PI * r.NextDouble();

                SphericalCoordinate sc;
                sc.Azimuth = azimuth;
                sc.Elevation = elevation;

                return sc;
            }

            public Vector3d GenerateRandomCoordinate()
            {
                double elevationSentinel = 0.5 * Math.PI * r.NextDouble();
                double elevation = elevationSentinel * r.NextDouble();
                double azimuth = 2 * Math.PI * r.NextDouble();

                return center + polarToCartesian( azimuth, elevation, radius );
            }

            private static Vector3d polarToCartesian( double azimuth, double elevation, double distance )
            {
                double x, y, z, k;

                k = Math.Cos( elevation );
                z = distance * Math.Sin( elevation );
                y = distance * Math.Sin( azimuth ) * k;
                x = distance * Math.Cos( azimuth ) * k;

                return new Vector3d( x, z, y );
            }
        }

        class BeamSimulator
        {
            private Geometry.Sphere head;

            public BeamSimulator( Geometry.Sphere head )
            {
                this.head = head;
            }

            public ParticleCoordinate TransformCoordinate( double azimuth, double elevation, double length )
            {
                ParticleCoordinate result = new ParticleCoordinate();

                result.S = length;

                Vector3d startPosition = polarToCartesian( azimuth, elevation, head.Radius );

                Vector3d startVelocity = elevationFactor1 * elevation * (startPosition - head.Center);

                if ( length < 0 )
                {
                    startVelocity.Normalize();
                    result.Position = startPosition + length * startVelocity;
                    result.Direction = startVelocity;
                    return result;
                }

                Vector3d prilahnutost = startPosition - head.Center;
                prilahnutost.Y = 0;
                prilahnutost.Normalize();
                startVelocity += elevationFactor2 * Math.Sqrt( elevation ) * prilahnutost;

                Vector3d gravity = -gravityFactor * Vector3d.UnitY;

                return simulateParticleMotion( startPosition, startVelocity, gravity, length );
            }

            private static ParticleCoordinate simulateParticleMotion( Vector3d startPosition, Vector3d startVelocity, Vector3d gravity, double length )
            {
                ParticleCoordinate result = new ParticleCoordinate();
                result.S = length;

                const double timeStep = 0.1;
                Vector3d position = startPosition;
                Vector3d velocity = startVelocity;
                double distanceTraveled = 0;

                while ( distanceTraveled < length )
                {
                    distanceTraveled += (velocity * timeStep).Length;
                    position += velocity * timeStep;
                    velocity += gravity * timeStep;
                }

                result.Position = position;

                velocity.Normalize();
                result.Direction = velocity;

                return result;
            }

            private static Vector3d polarToCartesian( double azimuth, double elevation, double distance )
            {
                double x, y, z, k;

                k = Math.Cos( elevation );
                z = distance * Math.Sin( elevation );
                y = distance * Math.Sin( azimuth ) * k;
                x = distance * Math.Cos( azimuth ) * k;

                return new Vector3d( x, z, y );
            }
        }
    }
}
